package principal;

import heuristicas.Heuristica;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AStar {

    private final Map<Integer, Vertice> abertos = new HashMap<>();
    private final Map<Integer, Vertice> fechados = new HashMap<>();
    private final Heuristica heuristica;

    public AStar(Heuristica h) {
        this.heuristica = h;
    }

    public List<Vertice> caminho(Vertice vInicial, Vertice vFinal) {
        vInicial.setH(heuristica.calcula(vInicial));

        abertos.put(vInicial.chave(), vInicial);

        while (!abertos.isEmpty()) {
            Vertice melhor = Collections.min(abertos.values(),
                    (v1, v2) -> {
                        if (v1.equals(vFinal)) {
                            return -1;
                        } else if (v2.equals(vFinal)) {
                            return 1;
                        } else {
                            return v1.getF() - v2.getF();
                        }
                    });

            if (melhor.equals(vFinal)) {
                return montaCaminho(melhor);
            }

            abertos.remove(melhor.chave());
            fechados.put(melhor.chave(), melhor);
            
            for (Vertice sucessor : melhor.geraSucessores()) {
                /*
                Integer chaveSucessor = sucessor.chave();

                Vertice jaExistente = abertos.get(chaveSucessor);
                if (jaExistente == null) {
                    jaExistente = fechados.get(chaveSucessor);
                }

                if (jaExistente == null) {// não visitado
                    sucessor.setH(heuristica.calcula(sucessor));
                    abertos.put(chaveSucessor, sucessor);
                } else if (sucessor.getG() < jaExistente.getG()) {// visitado
                    abertos.remove(chaveSucessor);
                    fechados.remove(chaveSucessor);

                    sucessor.setH(heuristica.calcula(sucessor));
                    abertos.put(chaveSucessor, sucessor);
                }*/
                if (visitado(sucessor)) {
                    Vertice jaExistente = new Vertice(Heuristica.puzzleDesejado);
                    if (abertos.containsKey(sucessor.chave())) {
                        for (Vertice x : abertos.values()) {
                            if (x.equals(sucessor)) {
                                jaExistente = x;
                                break;
                            }
                        }
                    } else {
                        for (Vertice x : fechados.values()) {
                            if (x.equals(sucessor)) {
                                jaExistente = x;
                                break;
                            }
                        }
                    }

                    if (sucessor.getG() < jaExistente.getG()) {
                        abertos.remove(jaExistente.chave());
                        fechados.remove(jaExistente.chave());
                        sucessor.setH(heuristica.calcula(sucessor));
                        abertos.put(sucessor.chave(), sucessor);
                    }
                } else {
                    sucessor.setH(heuristica.calcula(sucessor));
                    abertos.put(sucessor.chave(), sucessor);
                }
            }
        }

        return null;
    }
    
    private boolean visitado(Vertice v) {
        return abertos.containsKey(v.chave()) || fechados.containsKey(v.chave());
    }

    private List<Vertice> montaCaminho(Vertice v) {
        List<Vertice> caminho = new ArrayList<>();
        while (v != null) {
            caminho.add(v);
            v = v.getPai();
        }
        Collections.reverse(caminho);
        return caminho;
    }
}
